/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
 *
 */

import crypto from 'crypto';
import { Stats } from 'fs';
import fse from 'fs-extra';
import path from 'path';

import { DebugSymbol } from '../options/build/build-opt.js';

import { SoStripUtil } from './so-strip-util.js';
import { StripOptions } from '../tasks/worker/native-strip';

export class LibsFileCacheUtil {
  public static getFileHash(stats: Stats): string {
    return crypto
      .createHash('md5')
      .update(String(stats.size + stats.mtime.getTime()))
      .digest('hex');
  }

  /**
   * 根据一个文件的路径和cacheJson判断是否被改变过(用与判断processLibs目录中的文件)
   *
   * @param stripOptions strip的具体配置
   * @param fileAbsolutePath 文件的绝对路径
   * @param fileCacheJsonObj cacheJson对象 通过本类的readFileCacheJsonObj方法获取
   * @param intermediatesProcessLibs 文件存在的模块的processLib路径
   * @param debugSymbol 本次构建的strip选项
   * @return true 被修改过 false 没有被修改
   */
  public static async isSourceChanged(
    stripOptions: StripOptions,
    fileAbsolutePath: string,
    fileCacheJsonObj: NativeLibsCache,
    intermediatesProcessLibs: string,
    debugSymbol: DebugSymbol | undefined,
  ): Promise<boolean> {
    // 获取文件相对路径 判断上次是strip还是cp 本次是否有变化 如果有变化 则视为本文件变动了
    const fileRelativePath = path.relative(intermediatesProcessLibs, fileAbsolutePath);

    // 获取上次的编译时本文件是否被strip
    const lastStripped = this.isStripped(stripOptions, fileCacheJsonObj.debugSymbol, fileRelativePath, intermediatesProcessLibs);

    // 获取本次编译时本文件是否被strip
    const curStripped = this.isStripped(stripOptions, debugSymbol, fileRelativePath, intermediatesProcessLibs);

    if (curStripped !== lastStripped) {
      return true;
    }
    const jsonFileHash = fileCacheJsonObj.libs[fileAbsolutePath];
    return jsonFileHash === undefined || !fse.existsSync(fileAbsolutePath) || jsonFileHash !== LibsFileCacheUtil.getFileHash(fse.statSync(fileAbsolutePath));
  }

  private static isStripped(stripOptions: StripOptions, debugSymbol: DebugSymbol | undefined, fileRelativePath: string, processLibs: string) {
    const collectFiles = debugSymbol?.strip ?? true ? SoStripUtil.filesExcluding : SoStripUtil.matchedFiles;
    return collectFiles(stripOptions.collectAllLibs, debugSymbol?.exclude, processLibs).some((p) => path.normalize(p) === path.normalize(fileRelativePath));
  }

  /**
   * 根据一个文件的路径和cacheJson判断是否被改变过(用于判断strippedProcessLibs目录中的文件)
   *
   * @param fileAbsolutePath 文件的绝对路径
   * @param libsFileCacheJsonObj cacheJson对象 通过本类的readFileCacheJsonObj方法获取
   * @return true 被修改过 false 没有被修改
   */
  public static async isSinkChanged(fileAbsolutePath: string, libsFileCacheJsonObj: NativeLibsCache) {
    const jsonFileHash = libsFileCacheJsonObj.stripped[fileAbsolutePath];
    return jsonFileHash === undefined || !fse.existsSync(fileAbsolutePath) || jsonFileHash !== LibsFileCacheUtil.getFileHash(fse.statSync(fileAbsolutePath));
  }

  /**
   * 根据一个绝对路径 生成这个绝对路径下所有文件的hash值并返回(递归处理子文件夹)
   *
   * @param absolutePath 绝对路径
   * @return hash值的json字符串
   */
  public static async generateFileHashesForDirectory(absolutePath: string): Promise<Record<string, string>> {
    if (!absolutePath || !path.isAbsolute(absolutePath)) {
      throw new Error(`Invalid ${absolutePath} path. It must be a non-empty absolute path.`);
    }
    const hashes: Record<string, string> = {};

    if (!fse.existsSync(absolutePath)) {
      return hashes;
    }

    async function recursiveReadDir(currentPath: string) {
      const files = fse.readdirSync(currentPath);
      for (const file of files) {
        try {
          const filePath = path.join(currentPath, file);
          const stats = fse.statSync(filePath);
          if (stats.isDirectory()) {
            await recursiveReadDir(filePath); // Recursively read subdirectories
          } else if (stats.isFile()) {
            hashes[filePath] = LibsFileCacheUtil.getFileHash(stats);
          }
        } catch (err) {
          // 可能存在多线程并发下的文件操作问题，例如这里fse.readdirSync(currentPath);读取到了的文件
          // fse.statSync(filePath);的时候被删除了  所以这里只捕获文件不存在这一种异常 其他异常抛出
          if (err instanceof Error && 'code' in err && (err as any).code === 'ENOENT') {
            // 只有当错误不是文件不存在（ENOENT）时才抛出
          } else {
            throw err;
          }
        }
      }
    }

    await recursiveReadDir(absolutePath); // Start the recursive directory read
    return hashes;
  }

  /**
   * 刷新libs file的缓存
   *
   * @param originalLibs
   * @param strippedLibs
   * @param debugSymbol 用户填写的debugSymbol选项
   * @param cacheFilePath 缓存文件的路径
   */
  static async refreshLibsFileCache(originalLibs: string, strippedLibs: string, cacheFilePath: string, debugSymbol?: DebugSymbol) {
    // 读取processLibs目录和processStrippedNativeLibs目录下的所有文件 形成一个json字符串
    const libsCache = fse.existsSync(originalLibs) ? await LibsFileCacheUtil.generateFileHashesForDirectory(originalLibs) : {};
    const strippedCache = fse.existsSync(strippedLibs) ? await LibsFileCacheUtil.generateFileHashesForDirectory(strippedLibs) : {};

    // 合并两个Cache
    const resultJson: NativeLibsCache = {
      libs: libsCache,
      stripped: strippedCache,
      debugSymbol: debugSymbol,
    };
    fse.ensureDirSync(path.dirname(cacheFilePath));

    // 写入文件
    await fse.writeFile(cacheFilePath, JSON.stringify(resultJson), 'utf8');
  }
}

export interface NativeLibsCache {
  libs: Record<string, string>;
  stripped: Record<string, string>;
  debugSymbol: DebugSymbol | undefined;
}
